﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Djs.Tools.Tresor.Data
{
    /// <summary>
    /// Klíč k trezoru.
    /// Naprosto jednoduchá věc: sada náhodně rozmístěných bitů nastavených na 0 nebo 1.
    /// Obsah klíče nemá žádnou logiku.
    /// </summary>
    internal class TresorKey
    {
        #region Konstrukce, načtení ze souboru
        internal static TresorKey CreateFromFile(string fileName)
        {
            TresorKey key = new TresorKey(fileName);
            key.LoadFromFile(fileName, "");
            return key;
        }
        internal static TresorKey CreateFromFile(string fileName, string password)
        {
            TresorKey key = new TresorKey(fileName);
            key.LoadFromFile(fileName, password);
            return key;
        }
        internal static TresorKey CreateFromBuffer(byte[] buffer)
        {
            TresorKey key = new TresorKey();
            key.LoadFromBytes(buffer, "");
            return key;
        }
        internal static TresorKey CreateFromBuffer(byte[] buffer, string password)
        {
            TresorKey key = new TresorKey();
            key.LoadFromBytes(buffer, password);
            return key;
        }
        private TresorKey(string fileName)
            : base()
        {
            this._FileName = fileName;
        }
        private TresorKey()
        {
            this._FileName = null;
        }
        private void LoadFromFile(string fileName, string password)
        {
            System.IO.FileInfo fi = new System.IO.FileInfo(fileName);
            if (!fi.Exists)
                throw new ArgumentException("Klíčový soubor neexistuje.");
            long length = fi.Length;
            if (length < LENGTH_MIN)
                throw new ArgumentException("Klíčový soubor je příliš malý na to, aby byl platným klíčem.");
            if (length > LENGTH_MAX)
                length = LENGTH_MAX;

            // Načtu obsah souboru, nejvýše v dané délce (tím umožníme používat i extrémně dlouhé soubory, z nichž načtu jen počátek):
            long remaining = length;
            byte[] data = null;
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
            using (var stream = fi.OpenRead())
            {
                int cluster = 4096;
                byte[] buffer = new byte[cluster];
                while (remaining > 0L)
                {
                    int size = stream.Read(buffer, 0, cluster);
                    if (size <= 0)
                        break;
                    ms.Write(buffer, 0, size);
                    remaining -= (long)size;
                }
                stream.Close();
                data = ms.ToArray();
            }

            // Načtu klíč z bufferu:
            this.LoadFromBytes(data, password);
        }
        private void LoadFromBytes(byte[] buffer, string password)
        {
            long length = buffer.LongLength;
            if (length < LENGTH_MIN)
                throw new ArgumentException("Klíčový soubor je příliš malý na to, aby byl platným klíčem.");
            if (length > LENGTH_MAX)
                throw new ArgumentException("Klíčový soubor je příliš velký na to, aby byl platným klíčem.");
            this._Buffer = buffer;
            Strings.Test();
            this.DetectJustified();
            this.UnlockWithPassword(password);
        }
        /// <summary>Název souboru. Po změně hesla je klíč uložen nazpět.</summary>
        private string _FileName;
        /// <summary>Buffer = kopie obsahu klíčového souboru, beze změn, v délce nejvýše 64 KB</summary>
        private byte[] _Buffer;
        /// <summary>Délka dat v bufferu</summary>
        private int _Length { get { return (this._Buffer == null ? 0 : this._Buffer.Length); } }
        /// <summary>true pokud jde o klíč, který je zarovnaný = může obsahovat heslo. False pokud jde o RAW klíč (což může být například i fotografie). Takový klíč nemá heslo.</summary>
        private bool _IsJustified;
        /// <summary>true tehdy, když klíč má mít heslo</summary>
        private bool _HasPassword;
        /// <summary>
        /// Maska aplikovaná na CRC32 odheslovaného obsahu klíče. Kontroluje správnost použitého hesla.
        /// Pokud je 0, pak je přijato každé heslo (ztíží to útok na heslo, ale neposlouží uživateli který zadal chybné heslo = i chybné heslo bude přijato a použito).
        /// Pokud je 0xFFFFFFFF, pak se kontrola provádí důkladně, ale pomůže to útočníkovi, jemuž okamžitě poskytneme informaci že testované heslo je/není přijatelné.
        /// </summary>
        private UInt32 _PasswordMask;
        /// <summary>true pokud po aplikaci hesla došlo k platnému otevření klíče</summary>
        private bool _IsUnlocked;
        /// <summary>Datové kódy, načtené z bufferu a otevřené heslem. NULL pokud IsUnlocked je false (klíč není otevřen platným heslem).</summary>
        private EndlessBuffer _Codes;
        /// <summary>Minimální délka souboru = 128 Byte</summary>
        private const long LENGTH_MIN = 0x80;
        /// <summary>Maximální načítaná délka souboru = 65 KByte</summary>
        private const long LENGTH_MAX = 0x00010000;
        #endregion
        #region Vytvoření nového klíče
        internal static void SignWithPassword(byte[] buffer, string password)
        {

        }
        /// <summary>
        /// Vytvoří a vrátí nový klíč.
        /// Klíč je ve stavu Raw = není Justified, a nemá (ani nemůže mít) heslo.
        /// Klíč v tomto stavu je možno dále promíchávat (randomizovat) voláním metody Randomize(int random).
        /// Klíč je možno justifikovat voláním metody Justify() (pak už nejde randomizovat), a po Justify() je možno do klíče vložit heslo (StorePassword()).
        /// </summary>
        /// <returns></returns>
        internal static TresorKey CreateNew()
        {
            TresorKey key = new TresorKey();
            key._Rand = new Random(DateTime.Now.Millisecond);
            byte[] buffer = key._CreateRandomBuffer();
            key.LoadFromBytes(buffer, "");
            return key;
        }
        /// <summary>
        /// Změní náhodné místo v bufferu.
        /// Lze volat je pokud CanRandomize je true. Následně je možno volat metodu Justify(), a po Justify() je možno do klíče vložit heslo (StorePassword()).
        /// Metoda vrací index adresy, kde došlo k randomizaci (každé jedno volání změní jeden byte).
        /// </summary>
        /// <param name="random"></param>
        /// <returns></returns>
        internal int Randomize(int random)
        {
            if (!this.CanRandomize)
                throw new InvalidOperationException("Nelze provést randomizaci, klíč není v odpovídajícím stavu. Randomizaci lze provést pouze na novém klíči (vytvořen metodou TresorKey.CreateNew()) před jeho justifikací.");

            int index = _Rand.Next(_MetadataCodeBeginIndex, this._Length);
            this._Buffer[index] = (byte)(this._Buffer[index] ^ _Rand.Next(0, 255) ^ random);
            return index;
        }
        private byte[] _CreateRandomBuffer()
        {
            byte[] buffer = new byte[0x008000];
            int length = buffer.Length;
            _Rand.NextBytes(buffer);
            return buffer;
        }
        /// <summary>Generátor náhodných čísel, existuje jen po dobu od vytvoření nového klíče před jeho Justify()</summary>
        internal Random Rand { get { return this._Rand; } } private Random _Rand;
        /// <summary>True pokud je možno volat metodu Randomize(int random), je true jen po dobu od vytvoření nového klíče před jeho Justify()</summary>
        internal bool CanRandomize { get { return (this._Rand != null && !this._IsJustified); } }
        #endregion
        #region Justified nebo Raw: detekce IsJustified, detekce HasPassword a PasswordMask
        /// <summary>
        /// Metoda detekuje, zda je klíčový souboru Justified nebo Raw.
        /// Výsledek ukládá do proměnné IsJustified.
        /// Pokud je IsJustified, pak určí hodnoty pro práci s heslem: HasPassword a PasswordMask
        /// </summary>
        private void DetectJustified()
        {
            this._HasPassword = false;
            this._PasswordMask = 0;

            this._MetadataBeginIndex = (this._Length < 256 ? 4 : 16);

            byte[] buffer = this._Buffer;
            UInt32 crc32real = Crc32.Calculate(buffer, _MetadataCodeBeginIndex, _MetadataCodeLength);        // Vypočteme hodnotu CRC32 datové části bufferu
            UInt32 crc32save = Bits.ReadUInt32(buffer, _MetadataBufferCrcIndex);                             // Načteme otisk CRC32 z klíče
            this._IsJustified = (crc32real == crc32save);

            if (this._IsJustified)
            {
                byte p = buffer[_MetadataPasswordSettingIndex];
                this._HasPassword = ((p & 0x80) != 0);
                if (this._HasPassword)
                    this._PasswordMask = _GetPasswordMask((p & 0x1F));
            }
        }
        /// <summary>
        /// Vrátí masku pro CRC32 podle daného indexu. Pro index 0 (a menší) vrátí 0, pro index 32 (a větší) vrátí 0xFFFFFFFF, pro indexy 1 až 31 vrátí masku obsahující daný čat bitů v 1.
        /// Pro stejný index vždy vrací stejnou hodnotu.
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        private UInt32 _GetPasswordMask(int p)
        {
            return (p <= 0 ? (UInt32)0 : (p >= 32 ? (UInt32)0xFFFFFFFF : _BitMask[p]));
        }
        /// <summary>Masky pro kontrolu CRC32. Maska na indexu [0] obsahuje 0 nastavených bitů. Maska na indexu [31] obsahuje 31 nastavených bitů, a jeden bit v 0.</summary>
        private static readonly UInt32[] _BitMask =
        {
            0x00000000, 0x00000010, 0x00800080, 0x00800090, 
            0x41048000, 0x60403000, 0x00441850, 0x03028290, 
            0x04C20950, 0x1900F900, 0x9009940D, 0x2D0C6241, 
            0x92F4A120, 0x4B128764, 0x0C253AE6, 0x62903AF5, 
            0xA11BDE16, 0xC93531F3, 0x3BFC9CE0, 0xB57B3157, 
            0x296CEFE7, 0x7FBE2B72, 0xF6C756FD, 0xDA33DDFF, 
            0xDFFDF4AD, 0x97CFFDDF, 0xDECF7FFB, 0xFFBFBF97, 
            0xFFDB7BFF, 0x7FDBFFFF, 0xFDFFFBFF, 0xFFFFBFFF
        };
        /// <summary>
        /// Tato metoda vygeneruje sadu masek pro použití v poli _BitMask.
        /// Masky musí být konstantní, takže se v kódu citují jako readonly pole, ale nějak vzniknout musely :-).
        /// </summary>
        private void MaskMake()
        {
            UInt32[] bits = new UInt32[32];
            for (byte b = 0; b < 32; b++)
                bits[b] = Bits.GetMask321(b);                           // Hodnoty: 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0011, atd

            string result = "";
            UInt32[] masks = new UInt32[32];
            Random rnd = new Random();
            for (int i = 0; i < 32; i++)                                // 32 výsledků: [0] má nastaveno 0 bitů na hodnotu 1, [1] má jeden bit 1, ..., [12] má 12 bitů v jedničce...
            {
                List<UInt32> disp = new List<uint>(bits);               // Pracovní sada hodnot, kde je v každé hodnotě je nastaven vždy jen jeden bit, v každém prvku jiný.
                UInt32 mask = (UInt32)0;
                for (int b = 0; b < i; b++)                             // Do masky nastavím tolik bitů na hodnotu 1, kolik odpovídá číslu "i"
                {
                    int x = rnd.Next(0, disp.Count);                    // Index hodnoty, tento bit se přenese do masky.
                    mask |= disp[x];
                    disp.RemoveAt(x);                                   // Tato hodnota je už použití, příště se nepoužije.
                }
                masks[i] = mask;
                result += "0x" + mask.ToString("X8") + ", ";
            }
            System.Windows.Forms.Clipboard.SetText(result);
        }
        /// <summary>Index počátku metadat</summary>
        private int _MetadataBeginIndex { get; set; }
        /// <summary>Index počátku CRC32 bufferu. Pokud CRC odpovídá, pak klíč je v režimu IsJustified. Pokud neodpovídá (klíč je náhodný), pak je klíč v režimu Raw.</summary>
        private int _MetadataBufferCrcIndex { get { return _MetadataBeginIndex; } }
        /// <summary>Index informace o hesle. Bit 7 určuje přítomnost hesla. Bity 0-5 definují sílu kontroly hesla = počet kontrolovaných bitů v CRC (0=žádná kontrola, 32=32 bitová kontrola). Silná kontrola usnadňuje útok na heslo, slabá kontrola nezaregistruje chybně zadané heslo od uživatele.</summary>
        private int _MetadataPasswordSettingIndex { get { return _MetadataBufferCrcIndex + 4; } }
        /// <summary>Index počátku CRC32 kódu po odheslování. Pokud CRC odpovídá, pak je heslo akceptováno. Pokud neodpovídá, pak je ohlášena chyba zadaného hesla. Kontrolují se jen některé bity, podle masky z indexu _MetadataPasswordSettingIndex.</summary>
        private int _MetadataPasswordCrcIndex { get { return _MetadataPasswordSettingIndex + 1; } }
        /// <summary>Index počátku CRC32 kódu po odheslování. Pokud CRC odpovídá, pak je heslo akceptováno. Pokud neodpovídá, pak je ohlášena chyba zadaného hesla. Kontrolují se jen některé bity, podle masky z indexu _MetadataPasswordSettingIndex.</summary>
        private int _MetadataCodeBeginIndex { get { return _MetadataPasswordCrcIndex + 4; } }
        /// <summary>Délka dat kódu.</summary>
        private int _MetadataCodeLength { get { return this._Length - _MetadataCodeBeginIndex; } }
        #endregion
        #region Práce s heslem
        /// <summary>
        /// Aplikuje dané heslo na klíč, otevře tento klíč.
        /// Heslo může být i prázdné nebo null.
        /// Metoda vrací true, pokud klíč bude daným heslem otevřen, nebo false když ne.
        /// Tatáž hodnota je pamatována v this.IsUnlocked.
        /// </summary>
        /// <param name="password"></param>
        internal bool UnlockWithPassword(string password)
        {
            EndlessBuffer pass = (this._IsJustified ? CreatePasswordBuffer(password) : EndlessBuffer.Empty);
            this.UnlockWithPassword(pass);
            return this._IsUnlocked;
        }
        /// <summary>
        /// Změní heslo na nově dodané.
        /// Změnu hesla lze provést pouze na otevřeném klíči (IsUnlocked = true).
        /// Zadání nového hesla provede překódování bufferu s klíčem tak, aby poté vracel s nově zadaným klíčem tatáž data, která vrací nyní.
        /// Fyzicky se přepíše bitová mapa. Klíč se uloží do souboru, z něhož byl načten.
        /// </summary>
        /// <param name="password"></param>
        internal void ChangePassword(string password)
        {
            if (!this._IsUnlocked)
                throw new InvalidOperationException("Nelze zamknout klíč heslem, protože dosud není odemčen původním platným heslem.");

            EndlessBuffer pass = CreatePasswordBuffer(password);
        }
        /// <summary>
        /// Vytvoří a vrátí buffer vytvořený z hesla.
        /// Od běžného bufferu vytvořeného z textu (metodou EndlessBuffer.CreateFromString()) se liší tím, že řeší ochranu cyklického hesla.
        /// Standardní EndlessBuffer vrací podkladová data cyklicky, tzn. že pro vstupní data ABCDE vrací postupně data ABCDEDCBABCDEDCBABCDEDCBA...
        /// Pokud by tedy bylo zadáno heslo ABCBABCB, pak by jej bylo možno úspěšně nahradit heslem ABC 
        /// (protože z obou těchto hesel by EndlessBuffer vracel cyklickou sekvenci ABCBABCBABCBABCBABCBABCB...).
        /// Nebo ještě snadněji: heslo AAAAA by bylo nahraditelné heslem A.
        /// Ochrana spočívá v přidání další sekvence za vstupní heslo.
        /// </summary>
        /// <param name="password"></param>
        /// <returns></returns>
        private static EndlessBuffer CreatePasswordBuffer(string password)
        {
            if (String.IsNullOrEmpty(password)) return EndlessBuffer.Empty;

            // Musí zůstat konstantní, nelze do další verze kódu změnit!!!
            string add = "+T%OP$dVCmBjSb,Mcs(14)JwG.AnuN:Ze?IEW>[68]FoK@akl_x^3-7*y=52;q`Uiz#tv9/hrLX!pg\\R'YH|Q&{0}fD~\"<";
            int length = password.Length;
            if (length > 0 && length < 30)
                add = add.Substring(length);

            int rem = 3;
            foreach (char c in add)
            {
                if (!password.Contains(c))
                {   // Za heslo přidám 3 znaky, které v něm ještě nejsou. Přidávám znaky konstantně, nikoli náhodně = vždy se stávají součástí hesla:
                    password += c.ToString();
                    if (rem-- <= 0) break;
                }
            }

            return EndlessBuffer.CreateFromString(password);
        }
        /// <summary>
        /// Aplikuje dané heslo na klíč, otevře jej.
        /// Heslo může být i prázdné.
        /// </summary>
        /// <param name="pass"></param>
        private void UnlockWithPassword(EndlessBuffer pass)
        {
            byte[] buffer = this._Buffer;
            int length = this._Length;
            byte[] codes = new byte[_MetadataCodeLength];                              // Prostor pro odheslovaný klíč
            int codePtr = 0;
            if (this._IsJustified)
            {   // Pouze Justified klíč může mít heslo:
                if (pass == null) pass = EndlessBuffer.Empty;
                for (int i = _MetadataCodeBeginIndex; i < length; i++)
                    codes[codePtr++] = Xor(buffer[i], pass.GetNext());

                // Porovnám CRC kódu:
                UInt32 crc32real = Crc32.Calculate(codes);                             // Vypočteme hodnotu CRC32 kódu
                UInt32 crc32save = Bits.ReadUInt32(buffer, _MetadataPasswordCrcIndex); // Načteme otisk CRC32 kódu z bufferu
                UInt32 passMask = this._PasswordMask;
                this._IsUnlocked = ((crc32real & passMask) == (crc32save & passMask)); // Pokud se hlídané bity shodují, je heslo akceptováno.
            }
            else
            {   // Raw klíč je vždy bez hesla:
                for (int i = _MetadataCodeBeginIndex; i < length; i++)
                    codes[codePtr++] = buffer[i];
                this._IsUnlocked = true;
            }
            this._Codes = new EndlessBuffer(codes);
        }
        private static byte Xor(byte a, byte b) { return (byte)((a ^ b) & 0x00FF); }
        #endregion
        #region Přímé čtení a zápis z adresy, property klíče
        /// <summary>
        /// Vrací / nastavuje obsah konkrétní pozice v bufferu.
        /// Čte/zapisuje data přímo z key bufferu, tedy neodemčená heslem, z fyzické pozice. Čtení kódů se provádí metodami ReadCode(), ReadCodes(), ReadBitArray32(), ReadBitArray64(), ReadUniqueValues()
        /// </summary>
        /// <param name="pointer"></param>
        /// <returns></returns>
        internal byte this[int pointer]
        {
            get { CheckPointer(pointer); return this._Buffer[pointer]; }
            set { CheckPointer(pointer); this._Buffer[pointer] = value; }
        }
        /// <summary>
        /// true pokud klíč je použitelný (je načten z patřičných dat, a je odemčen heslem)
        /// </summary>
        internal bool IsValid { get { return (this._Buffer != null && this._Buffer.Length >= 128 && this._IsUnlocked); } }
        /// <summary>
        /// Vrátí ukazatel pro čtení dat z klíče na výchozí pozici
        /// </summary>
        internal void Reset()
        {
            this._Codes.Reset();
        }
        /// <summary>
        /// Pozice = počet dosud přečtených dat z bufferu. Po Resetu = 0. Inkrementuje hodnotu po každé vrácené hodnotě.
        /// </summary>
        public long Position { get { return this._Codes.Position; } }
        private void CheckPointer(int pointer)
        {
            if (pointer < 0) throw new ArgumentException("Hodnota pointer nesmí být záporná.");
            if (pointer >  this._Buffer.Length) throw new ArgumentException("Hodnota pointer nesmí větší než je velikost načtených dat.");
        }
        #endregion
        #region Sekvenční čtení jednotlivých Byte
        /// <summary>
        /// Vrátí další byte z klíče. Metodu lze volat nekonečněkrát, vždy vrátí další byte.
        /// Buffer je dvojcyklický: nejprve vrací byte od pozice 0 do posledního, pak zpátky od předposledního do 0, a pak zase od 1 do posledního...
        /// </summary>
        /// <returns></returns>
        internal byte ReadCode()
        {
            return (this._IsUnlocked ? this._Codes.GetNext() : (byte)0);
        }
        /// <summary>
        /// Vrátí buffer načtený z klíče.
        /// </summary>
        /// <returns></returns>
        internal byte[] ReadCodes(int length)
        {
            byte[] buffer = new byte[length];
            for (int i = 0; i < length; i++)
                buffer[i] = this.ReadCode();
            return buffer;
        }
        #endregion
        #region Čtení z bitové desky (čte bity nezávisle na 8-mi bitovém uspořádání bufferu a vrací je jako pole UInt32 nebo UInt64)
        /// <summary>
        /// Vrátí požadovaný počet (count) hodnot o dané šířce slova (šířka slova = bits). Využije tak plně uložená data.
        /// </summary>
        /// <param name="count"></param>
        /// <param name="bits"></param>
        /// <returns></returns>
        internal UInt32[] ReadBitArray32(int count, byte bits)
        {
            Bits.CheckBits32(bits);

            List<UInt32> result = new List<UInt32>();

            UInt32 accumulator = 0;
            byte accWidth = 0;
            while (result.Count < count)
            {
                // Dokud v akumulátoru nemám alespoň tolik bitů, kolik je požadováno, tak do něj přidávám další data:
                while (accWidth < bits)
                {
                    UInt32 data = this.ReadCode();
                    if (accWidth > 0)
                        data = data << accWidth;
                    accumulator = accumulator | data;
                    accWidth += 8;
                }

                // V akumulátoru mám načteno přinejmenším tolik bitů, kolik je zapotřebí (možná i více). Odeberu si z něj, co potřebuji a zůstatek posunu doprava:
                result.Add(Bits.Mask(accumulator, bits));

                accumulator = accumulator >> bits;
                accWidth -= bits;
                if (accWidth < 0)
                    throw new InvalidOperationException("Chyba algoritmu GetNextArray(), akumulátor se zmenšil pod 0.");
            }

            return result.ToArray();
        }
        /// <summary>
        /// Vrátí požadovaný počet (count) hodnot o dané šířce slova (šířka slova = bits). Využije tak plně uložená data.
        /// </summary>
        /// <param name="count"></param>
        /// <param name="bits"></param>
        /// <returns></returns>
        internal UInt64[] ReadBitArray64(int count, byte bits)
        {
            Bits.CheckBits64(bits);

            List<UInt64> result = new List<UInt64>();

            UInt64 accumulator = 0;
            byte accWidth = 0;
            while (result.Count < count)
            {
                // Dokud v akumulátoru nemám alespoň tolik bitů, kolik je požadováno, tak do něj přidávám další data:
                while (accWidth < bits)
                {
                    UInt64 data = this.ReadCode();
                    if (accWidth > 0)
                        data = data << accWidth;
                    accumulator = accumulator | data;
                    accWidth += 8;
                }

                // V akumulátoru mám načteno přinejmenším tolik bitů, kolik je zapotřebí (možná i více). Odeberu si z něj, co potřebuji a zůstatek posunu doprava:
                result.Add(Bits.Mask(accumulator, bits));

                accumulator = accumulator >> bits;
                accWidth -= bits;
                if (accWidth < 0)
                    throw new InvalidOperationException("Chyba algoritmu GetNextArray(), akumulátor se zmenšil pod 0.");
            }

            return result.ToArray();
        }
        #endregion
        #region Čtení z bitové desky + vrácení seznamu UNIQUE hodnot
        /// <summary>
        /// Vrátí některé prvky ze seznamu v parametru "values", vybere z nich prvky na které ukazuje pole indexů načtené z this.
        /// Indexy se načítají v počtu (count) což bude počet prvků výstupního pole.
        /// Každý vstupní prvek bude do výstupu uložen nejvýše jedenkrát.
        /// Pokud počet prvků ve values[] je větší než count, pak se některé prvky z values do výstupu nedostanou (seznam nepoužitých bude vrácen do ref parametru values).
        /// Pokud počet prvků ve values[] je shodný s count, pak se do výstupu dostanou všechny prvky (a ref parametr values bude prázdný).
        /// Pokud počet prvků ve values[] je menší než count, dojde k chybě.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="count"></param>
        /// <param name="bits"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        internal T[] ReadUniqueValues<T>(int count, int bitCount, T[] values)
        {
            return this.ReadUniqueValues(count, bitCount, ref values);
        }
        /// <summary>
        /// Vrátí některé prvky ze seznamu v parametru "values", vybere z nich prvky na které ukazuje pole indexů načtené z this.
        /// Indexy se načítají v počtu (count) což bude počet prvků výstupního pole.
        /// Každý vstupní prvek bude do výstupu uložen nejvýše jedenkrát.
        /// Pokud počet prvků ve values[] je větší než count, pak se některé prvky z values do výstupu nedostanou (seznam nepoužitých bude vrácen do ref parametru values).
        /// Pokud počet prvků ve values[] je shodný s count, pak se do výstupu dostanou všechny prvky (a ref parametr values bude prázdný).
        /// Pokud počet prvků ve values[] je menší než count, dojde k chybě.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="count"></param>
        /// <param name="bits"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        internal T[] ReadUniqueValues<T>(int count, int bitCount, ref T[] values)
        {
            if (values.Length < count)
                throw new InvalidOperationException("Chyba algoritmu GetNextUniqueValues(), je dodáno málo zdrojů vzhledem k požadovanému počtu výsledků.");

            Bits.CheckBits32(bitCount);
            byte bits = (byte)bitCount;
            List<T> result = new List<T>();
            List<T> source = new List<T>(values);
            
            Queue<UInt32> indexes = new Queue<uint>(this.ReadBitArray32(count, bits));
            while (result.Count < count)
            {
                if (source.Count <= 0)
                    throw new InvalidOperationException("Chyba algoritmu GetNextUniqueValues(), došly unikátní zdroje.");

                Int32 index = (Int32)indexes.Dequeue();
                Int32 idx = (index % source.Count);
                result.Add(source[idx]);
                source.RemoveAt(idx);
            }

            values = source.ToArray();
            return result.ToArray();
        }
        #endregion
        #region Cyklický buffer: z dodaných dat dokáže vracet nové a nové byte.
        private class EndlessBuffer
        {
            #region Konstrukce a privátní záležitosti
            public EndlessBuffer(byte[] source) : this(source, false) { }
            private EndlessBuffer()
            {
                this.IsEmpty = true;
            }
            private EndlessBuffer(byte[] source, bool ignoreLength)
            {
                if (source == null)
                    throw new ArgumentException("EndlessBuffer vyžaduje zdrojová data, nepovoluje NULL");
                if (!ignoreLength && source.Length < 16)
                    throw new ArgumentException("EndlessBuffer vyžaduje zdrojová data v délce nejméně 16 Byte.");
                if (ignoreLength && source.Length < 1)
                    throw new ArgumentException("EndlessBuffer vyžaduje zdrojová data v délce nejméně 1 Byte.");

                this.Init(source);
            }
            private void Init(byte[] source)
            {
                this.IsEmpty = false;
                this.Source = source;
                this.Last = source.Length - 1;
                this.Reset();
            }
            /// <summary>
            /// Krokování skrz buffer
            /// </summary>
            private void _StepOne()
            {
                if (this.IsEmpty) return;

                this._Position++;                // Pozice se od resetu pouze inkrementuje

                int last = this.Last;            // Například pokud délka bufferu = 100, pak Last = 99 (poslední prvek je na indexu 99):
                this.Pointer += this.Step;       // Z adresy this.Pointer jsme právě vrátili data, posuneme se na další adresu ve směru aktuálního kroku...
                if (this.Pointer > last)         // Pokud by pointer šel za poslední pozici v bufferu(+1): 99 => 100
                {
                    this.Step = -1;              //  pak otočíme směr pro příští kroky (-1)
                    this.Pointer = (last == 0 ? 0 : last - 1);     //  a příští data vrátíme z adresy (99-1) = 98, tedy po datech z pozice [99] vrátíme data z pozice [98].
                }
                if (this.Pointer < 0)            // Pokud by nám pointer klesl pod 0, tak v další fázi půjdeme nahoru, počínaje od pozice [1] (protože pozici [0] jsme právě vrátili)
                {
                    this.Step = 1;
                    this.Pointer = (last == 0 ? 0 : 1);
                }
            }
            private byte[] Source;
            private bool IsEmpty;
            private int Last;
            private int Pointer;
            private int Step;
            #endregion
            #region Public prvky
            /// <summary>
            /// Vrátí další byte z bufferu. 
            /// EndlessBuffer je jako nekonečná slánka, nebo jako kouzelníkův klobouk: nemá konce, stále může dodávat další a další data.
            /// </summary>
            /// <returns></returns>
            public byte GetNext()
            {
                if (this.IsEmpty) return (byte)0;

                byte value = this.Source[this.Pointer];
                this._StepOne();
                return value;
            }
            /// <summary>
            /// Přeskočí daný počet byte od aktuální pozice.
            /// </summary>
            /// <param name="count"></param>
            internal void Skip(int count)
            {
                if (this.IsEmpty) return;
                for (int s = 0; s < count; s++) this._StepOne();
            }
            /// <summary>
            /// Resetuje buffer = nastaví na začátek.
            /// Po Resetu bude buffer dodávat vždy ta samá data jako po předešlém resetu (pokud má shodný zdroj).
            /// </summary>
            public void Reset()
            {
                if (this.IsEmpty) return;

                this.Pointer = 0;
                this.Step = 1;
                this._Position = 0L;
            }
            /// <summary>
            /// Vrátí empty buffer, který je prázdný, stále vrací hodnotu 0.
            /// </summary>
            /// <param name="text"></param>
            /// <returns></returns>
            public static EndlessBuffer Empty { get { return new EndlessBuffer(); } }
            /// <summary>
            /// Pozice = počet dosud přečtených dat z bufferu. Po Resetu = 0. Inkrementuje hodnotu po každé vrácené hodnotě.
            /// </summary>
            public long Position { get { return this._Position; } } private long _Position;
            /// <summary>
            /// Z dodaného textu vrátí EndlessBuffer.
            /// </summary>
            /// <param name="text"></param>
            /// <returns></returns>
            public static EndlessBuffer CreateFromString(string text)
            {
                List<byte> codes = new List<byte>();
                if (text == null)
                    text = "";
                foreach (char c in text)
                {
                    int value = (int)c;
                    byte lo = (byte)(value & 0x00FF);
                    codes.Add(lo);
                    byte hi = (byte)((value >> 8) & 0x00FF);
                    if (hi > 0)
                        codes.Add(hi);
                }
                while (codes.Count < 3)
                    codes.Add((byte)0);
                return new EndlessBuffer(codes.ToArray(), true);
            }
            #endregion

        }
        #endregion
    }
}
